home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
New Star Software Collection
/
NSS_Collection.iso
/
3-170 dbase 10 for windows
/
1.ima
/
DOC.PAK
/
OOPEXTEN.TXT
< prev
next >
Wrap
Text File
|
1993-07-26
|
22KB
|
626 lines
Copyright (c) 1991-1993 Borland International, Inc.
All Rights Reserved.
THE dBASE LANGUAGE OBJECT EXTENSIONS
------------------------------------
Application programmers are facing increasingly complex
programming requirements. For instance, building event-driven
Windows applications often means learning new techniques and
coordinating among several programmers writing many lines of
source code. To make your job easier, Bladerunner has enhanced
the dBASE language with new object oriented extensions.
This document introduces the object extensions and describes some
of the new techniques for using them. Another file, DBLANG.WRI,
contains specifications and syntax for the new extensions.
Contents
--------
I. Object Terminology
II. Who Needs Object Extensions?
III. Object-Orientated Design in dBASE
IV. Variable Scoping and Objects
V. Creating Objects
VI. Understanding Object Reference Variables
VII. Understanding Function Pointers
VIII. Creating New Classes
IX. Examples
Object Terminology
------------------
Before learning how object orientation applies to dBASE, you need
to become familiar with certain standard terminology of object-
oriented programming. The following definitions will give you an
idea of how these concepts are implemented in Bladerunner.
Object A collection of related memory variables. Objects
consist of properties and methods. An object is an
"instance" of a class. For example, a window you
create, with DEFINE WINDOW, is an instance of the
Window class.
Class A specification, or "recipe," for a type of
object. A class definition consists of two parts:
first, any dBASE code required for the construction of
an object, such as property assignments; second, method
declarations. Bladerunner provides many pre-defined
"stock" classes, such as Window and Menu. (The list of
stock classes appears in the on-line help.) You can also
create your own classes with the CLASS...ENDCLASS
statement.
Member An item contained in an object. An object's
members are properties and methods.
Property A memory variable contained in an object.
Properties define characteristics of an object, such as
size, location, or color. You can query the value of
any property, and change the value of most properties.
You can add new properties to an object with a simple
assignment statement.
Method A subroutine, such as a function or procedure,
associated with an object through a function-pointer
variable. Methods perform actions on an object. For
example, a method can change an object's position in a
window, or determine if the value of a property is
valid. Some methods execute automatically when an event
occurs; others execute only when called explicitly.
Event An occurrence, such as a mouse click. Events cause
methods to execute. For example, pressing the left
mouse button while pointing to a window object executes
the window's OnLeftMouseDown method.
Who Needs Object Extensions?
-------------------------------
Using traditional dBASE commands and techniques, you can easily
build procedural user interfaces, where a series of cascading
menu choices launch pre-defined procedures. However, these
techniques are not well-suited for creating event-driven
interfaces. In event-driven environments, like Windows, the user
controls program flow. A user can click a button, activate a
window, or select a menu choice at any time, and the program must
respond to these events in whatever sequence they occur.
To help build event-driven applications, Bladerunner provides UI
language extensions (described in UI_EXTEN.TXT). Using familiar
dBASE-like syntax, any dBASE programmer can define UI objects,
called "controls," and activate them in one or more windows.
Advanced programmers need the ability to change a control's
characteristics in response to an event. For instance, after
defining and displaying a window, your program might need to
change the window's size, position, or color, without destroying
the window and creating it again. The Bladerunner UI extensions
let you change certain control characteristics using the REDEFINE
commands. However, REDEFINE can't change all characteristics of a
control, tell you the value of a characteristic, or add new
characteristics.
Developers writing sophisticated user interfaces need the ability
to change, query, and even add, any characteristic to a control.
The Bladerunner object extensions make this easy.
Object-Oriented Design in dBASE
-------------------------------
This section introduces some of the concepts behind object-
oriented programming, then shows how you can apply them to dBASE.
Focus on the concepts here; the details for implementing them are
explained later.
Three main concepts characterize an object-oriented programming
environment:
Encapsulation Combining data with the subroutines that operate
on it to form a new structure called an object.
Inheritance Defining a type, or class, of object and then
using it to build a hierarchy of descendant
objects, with each descendant "inheriting" the
data and subroutines of its ancestors.
Polymorphism Giving an object in a hierarchy the ability to
implement an action that is common to all objects
in the hierarchy, in whatever way is most
appropriate for that object.
Suppose you write programs for a small business. In one of your
applications, you create a window for displaying employee
records, and another for displaying customer information. For
each window, you store field values to memory variables for
displaying and editing.
Next, suppose you want to display both windows at the same time.
Doing so, you risk having employee variables conflict with
variables defined for the customer information window. For
instance, if you display the address for both employees and
customers, you may overwrite the City, State, or Zip variables of
an employee with those of a customer.
To solve this, you can encapsulate the employee data, and the
subroutines for manipulating it, into an Employee object.
Likewise, you can encapsulate the customer data and subroutines
into a Customer object. Then, instead of displaying a regular
memory variable for each address item, you display the City,
State, and Zip "properties" of the Employee or Customer objects.
Encapsulation "hides" data in an object; so the properties of the
Employee object can co-exist with the properties of the Customer
object. When you encapsulate data into objects, you reduce the
risk that one programmer's work will interfere with another's.
Next, suppose you want to create a new window for editing
prospective customers who haven't yet placed an order. You need
to store the same information as a customer, plus some new
fields. Instead of creating a new window from scratch, you can
create a Prospect object based on the Customer object. The
Prospect object inherits the characteristics of the Customer
object.
Inheritance is essentially programming by behavior modification.
Different objects that share some common data or behavior can
both inherit that information from an existing object, and then
you can change only what's needed. Inheritance makes your code
much easier to re-use.
Next, suppose you need to display information for both full-time
and part-time employees. Both employee types need to store the
same information, except that part-timers don't get vacation pay.
You can start by creating a PartTime object based on the Employee
object. The Employee object has a subroutine, CalculatePay(), for
calculating the yearly compensation. In the PartTime object, you
can override CalculatePay() with another function that excludes
vacation pay. This is an example of polymorphism.
The dBASE SKIP command illustrates the benefits of polymorphism.
SKIP works differently depending on the status of the current
work area. If the work area is indexed, SKIP moves to the next
record in the index; if a filter is set, SKIP moves to the next
record meeting the filter condition. You don't need to tell SKIP
about the work area; you just SKIP and it knows what to do.
Likewise, your program can retrieve the yearly compensation of an
employee the same way, by invoking CalculatePay(), regardless of
the type of employee. By "hiding" the implementation of an
action, you are free to change the details of the implementation
without requiring changes to the interface. Suppose full-timers
get a quarterly bonus; you just change CalculatePay() for the
Employee object. Polymorphism enhances the reusability of your
code.
Variable Scoping and Objects
----------------------------
To understand how Bladerunner encapsulates variables into
objects, you need to understand variable scoping. This section
introduces objects as a solution to common dBASE scoping
problems.
The scope of a memory variable is defined by
- the variable's lifespan; that is, under what circumstances a
variable is released or destroyed.
- the variable's visibility; that is, under what circumstances a
program can access or modify a variable.
Bladerunner offers two new ways to specify memory variable
scopes. First, the new LOCAL command declares variables to be
local to the procedure or function in which they are created.
LOCAL variables are similar to PRIVATE, except that LOCAL
variables are not visible in subsequently called routines.
Second, the new STATIC command declares variables that are LOCAL
in visibility but PUBLIC in lifespan.
The following table summarizes the lifespan and visibility of
dBASE variable types:
Lifespan Visibility
---------------------------- -----------------------
PUBLIC Destroyed only when released Everywhere
PRIVATE Destroyed when creating Creating routine, and
routine ends subsequent routines
LOCAL Destroyed when creating Creating routine only
routine ends
STATIC Destroyed only when released Creating routine only
You can declare LOCAL and STATIC variables in your procedural
code to protect against inadvertant overwriting of data, and
increase program modularity. Also, when you create objects,
variables that are members of an object are automatically LOCAL
in scope.
Creating objects
----------------
Create an object using the NEW operator in a memory variable
assignment statement. NEW creates a new object, and creates an
"object reference" variable that refers to the object. (See
"Understanding Object Reference Variables" below.) The following
example creates a new Window object and makes MyWin refer to it:
MyWin = NEW Window()
Use the member access operator (.) to refer to a member of an
object. The member access operator, or "dot" operator, associates
members to an object much the same way an alias operator (->)
associates a field with a particular table.
MyWin.Left = 200 && Move the left border to position 200.
MyWin.Visible = .F. && Make MyWin invisible
Objects are extensible. You can add new members to an object with
a simple assignment statement.
MyWin.MyPropN = 123 && Adds a new property called MyPropN
MyWin.MyPropC = "yo" && Adds a new property called MyPropC
Understanding Object-Reference Variables
----------------------------------------
Traditional dBASE memory variables contain values; a numeric
variable contains a numeric value. In contrast, an object-
reference variable contains a reference to an object, not the
object itself.
You create a new object-reference variable the same way you
create other variables, with a simple assignment statement. To
create a new object, however, use the NEW operator in an
assignment statement.
This example demonstrates normal dBASE memory variable
assignments:
X = 5 && X contains the value 5
Y = X && Y contains the value 5
X = 6 && X contains the value 6
? X && Returns 6
? Y && Returns 5
Now compare the previous example to the following example, using
object-reference variables:
MyWin = NEW Window() && Create a Window object and create
&& MyWin referring to the window.
? TYPE("MyWin") && Returns "O" for object reference
MyWin.X = 10 && Add new property X
MyWin.Y = 20 && Add new property Y
YourWin = MyWin && MyWin and YourWin both refer to
&& the same window
? YourWin.X && Returns 10
YourWin.Y = 30 && Changes property Y to 30
? YourWin.Y && Returns 30
? MyWin.Y && Returns 30
YourWin = "text" && YourWin is now a normal char variable
? MyWin.Y && Still returns 30
MyWin = "text" && MyWin is now a normal char variable
&& The window object is destroyed
The previous example demonstrates three important points about
object-reference variables:
- More than one object-reference variable can refer to the same
object.
- When you change an object, all references to the object reflect
the change.
- An object exists as long as an object-reference variable refers
to it. When you release or re-assign all object-reference
variables referring to an object, the object is destroyed.
You work with object-reference variables the same way you work
with other memory variables. For instance, you can pass them as
parameters, return them as function results, and store them in
arrays.
Understanding Function Pointers
-------------------------------
A method is a subroutine associated with an object through a
function pointer variable, a new variable type. Function pointers
refer indirectly to a function. They can be copied, passed as
parameters, returned as values from functions, and used in the
same way as traditional variables.
Function pointers call functions indirectly using the call
operator "()". For example:
pFunc = Ten && Assigns Function Ten to pFunc
? pFunc() && Returns 10
Function Ten
RETURN 10
Creating New Classes
--------------------
You can define a new class using the CLASS...ENDCLASS statement.
The following example defines a class with two properties,
creates an object of that class, and queries the property values:
X = NEW Numbers() && Creates a new Numbers object
? X.Ten && Returns 10
? X.Twenty && Returns 20
CLASS Numbers
MEMBER Ten, Twenty
Ten = 10
Twenty = 20
ENDCLASS
Using MEMBER or.This to Reference Properties
The MEMBER statement in class definitions declares properties.
Bladerunner also provides an alternate syntax for referencing
properties in classes without explicitly declaring them. To
reference properties not declared in a MEMBER statement, preface
the property name with "This.".
This. acts like a place holder for the object name created from
the class.
The following two class definitions are semantically identical:
A = NEW Square2()
A.Num = 5
? A.Value() && Returns 25
CLASS Square1
MEMBER Num
Num = 0
FUNCTION Value
RETURN Num * Num
ENDCLASS
CLASS Square2
This.Num = 0
FUNCTION Value
RETURN This.Num * This.Num
ENDCLASS
Defining and Passing Parameters
You define and pass parameters for classes by placing the
parameters in parentheses at the end of the class name.
Parameters you pass are LOCAL in scope. The following example
defines a class, Square, and passes a parameter when creating a
Square object:
X = NEW Square(10) && Creates a new Square object
? X.SquareNum && Returns 100
Y = NEW Square(5) && Creates a new Square object
? Y.SquareNum && Returns 25
CLASS Square(n)
MEMBER Num, SquareNum
Num = n
SquareNum = n*n
ENDCLASS
Note: In this Alpha release, declaring parameters in parentheses
is not implemented. You can instead declare parameters with a
PARAMETERS statement as follows:
CLASS SQUARE
PARAMETERS n
ENDCLASS
Defining and calling methods
You define methods for a class using a FUNCTION declaration
within the class definition. Follow the same rules for declaring
normal dBASE UDFs.
In the previous Square example, the value of property X.SquareNum
is computed when you create the object. By declaring Value as a
method instead of a property, you can compute Value at any time.
The following example demonstrates this:
X = NEW Square2() && Create a new Square2 object
X.Num = 5 && Change the value of Num
? X.Square() && Returns 25
X.Num = 6 && Change the value of Num again
? X.Square() && Returns 36
CLASS Square2
MEMBER num
Num = 0
FUNCTION SquareNum
RETURN Num * Num
ENDCLASS
Note: If you assign the same name to a method and a property, the
property is assumed in expressions. Internally, Bladerunner
treats methods and properties the same; a method is a property
whose data type is function-pointer.
This example assigns the name Ten to both a method and a
property. Notice the return values:
Function Ten
RETURN 10
Ten = 10
pFunc = Ten && assigns value 10 to pFunc
? pFunc() && ERROR: data type mismatch
A subroutine can serve as a method for more than one object. The
following example demonstrates this:
O = NEW Object()
O.x = 10
O.addOne = FuncAddOne
? O.addOne() && Returns '11'
? O.x && Returns '11'
Y = NEW Object()
Y.x =30
Y.z = FuncAddOne
? Y.z() && Returns '31'
FUNCTION FuncAddOne
this.x = this.x + 1
RETURN this.x
Examples
--------
Creating a class based on another class - Inheritance
You can create a class that is derived from, or "inherits," the
members of another class. The parent class can be a stock class,
such as Window, or a class you previously created. This technique
is called sub-classing and demonstrates the concept of
inheritance. Subclasses inherit all properties and methods of the
parent from which they are derived.
Use the OF option in the CLASS definition statement to specify
the parent class from which to inherit members. The following
example defines a class called Parent, and derives another class
from Parent. The Derived class inherits all the members of
Parent, and adds a few more.
b = NEW Parent() && prints 'first '
? b.X && prints 10
? b.Y && prints 20
d = NEW Derived() && prints 'first second'
? d.X && prints 10
? d.Y && prints 20
? d.Z && prints 100
CLASS Parent
MEMBER X, Y
? "first "
X = 10
Y = 20
ENDCLASS
CLASS Derived OF Parent
MEMBER Z
?? "second"
Z = 100
ENDCLASS
Overriding Parent Members - Polymorphism
When you derive a new class from a parent class, you can override
parent members by simply re-initializing the variables or re-
declaring the subroutines.
The technique of overriding parent members is an example of
polymorphism. Polymorphism lets you treat similar objects in a
uniform fashion.
This example defines a Parent class and derives a class from
Parent, overriding one of Parent's methods:
A = NEW Parent()
B = NEW Derived()
? A.One() && Prints 'Parent 1'
? A.Two() && Prints 'Parent 2'
? B.One() && Prints 'Parent 1'
? B.Two() && Prints 'Derived 2'
CLASS Parent
FUNCTION One
RETURN "Parent 1"
FUNCTION Two
RETURN "Parent 2"
ENDCLASS
CLASS Derived OF Parent
FUNCTION Two && Override Method Two
RETURN "Derived 2"
ENDCLASS
Arrays are Objects
An array is a homogenous collection of variables; an object is a
heterogeneous collection.
Arrays are a kind of object. Arrays use the index operator to
refer to contents. Arrays also have properties. An array has the
two built-in properties, size and dimensions. Arrays are objects,
so variables that refer to arrays are of type object reference.
More than one variable can refer to the same array.
decl a[10]
? a.size && prints 10
? a[1] && prints false
a[1] = 10
b = a && b and a refer to the same array
? b[1] && prints 10
b[1] = 20
? a[2] && prints 20
? a.fill("hello")
? a[4] && prints 'hello'
Like all other objects, arrays are extensible. Variables can be
added to arrays via assignment. For example:
decl a[10]
a.myprop = "hello" && add variable 'myprop' to
? a.myprop && prints "hello"
Passing Properties by Reference
Pass properties by reference the same way you pass other memory
variables. You can also pass array elements by reference.
O = NEW Object()
O.X = 20
DO Func WITH O.X
? O.X && prints 30
PROCEDURE Func
PARAMETERS Y
Y = Y + 10
RETURN
Returning Object-Reference Variables in Functions
Object-reference is a valid return type for a function. For
example:
O = MakeObject() && Creates a new object
? O.X && Returns 10
FUNCTION MakeObject
PRIVATE x
x = NEW Object()
x.x = 10
RETURN x && Returns an object-reference var
Passing Parameters to Base Classes
You can pass parameters to base classes by including them in
parenthesis after the class name. For example:
NOTE: The following code will not execute in this Alpha release.
CLASS MyCube(n) OF MySquare(n)
MEMBER CubeValue
CubeValue = Num * Square
ENDCLASS
CLASS MySquare(n)
MEMBER Num, Square
Num = n
Square = n*n
ENDCLASS